#ifdef _WIN32
#include "Core\Oatmeal.h"			// standard library include (must be first include)
#include "Core\Internals.h"			// standard include for intenral definitions
#include "Core\Error.h"				// error handling routines
#include "IO\Settings.h"			// module to retrieve/set engine settings
#include "GUI\Windowing.h"			// standard windowing routines

////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////// WINDOWING ROUTINES FOR WIN32/WIN64 ////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////

// private member variables
static HICON m_hMainIcon = NULL;					// handle to the small icon to use for the main window
static HICON m_hMainIconSmall = NULL;				// handle to the small icon to use for the main window
static HANDLE m_hMainWndMutex = NULL;				// handle to the single instance mutex
static tchar m_szMainClassName[MAX_STRING] = {0};	// string to contain the class name for the main window
static bool m_bShowMaximized = false;				// don't get me started on why I need this one
static float32 m_fAspectRatio = 0.0f;				// does the main window maintain its aspect ratio?
static OATONCLOSEPROC m_pfOnClose = NULL;			// pointer to event handler for closing the main window

// private macros
#define WINDOW_CLASS_GUID	_T("3C17820431264664BF63E746DB1E3F74")
#define WINDOW_POS_SIZE_KEY	_T("OAT539MWP") // 0x539 is 1337 :o), MWP stands for Main Window Placement
#define WINDOW_MAX_KEY		_T("OAT539MWM") // 0x539 is 1337 :o), MWM stands for Main Window Maximized

// private routines
static bool				_GoFullscreen		(uint32 nWidth, uint32 nHeight, uint8 nBPP, uint8 nVertRefresh);
static void				_GoWindowed			(void);
static LRESULT CALLBACK _ProcMainWnd		(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam);

///////////////////////////////////////////////////////////////////////////////////////////////////

/*/
/ / LRESULT CALLBACK
/ /		_MainWndProc (HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
/ /
/ /		hWnd = handle to the window the message belongs to
/ /		uMsg = message to process
/ /		wParam = word sized param who's value depends on the message
/ /		lParam = long sized param who's value depends on the message
/ /
/ / PURPOSE:
/ /		Window procedure to handle messages for the main window.
/*/

OATINLINE(bool)
apiCreateMainWindow (const size *pDefaultSize, const color *pBackground,
				  const bool bResizable, const bool bForceSize)
{
	bool bReturn = false;

	// only bother if the init routine has been called
	if(GlobalSettings.bInitialized)
	{
		// update/set the class name the main window will use, use the following format to ensure uniqueness...
		// [GUID] [Main Window Identifier] [App Name] [Company Name]
		SecureZeroMemory(m_szMainClassName, SIZE(m_szMainClassName));
		SPRINTF(m_szMainClassName, _T("%s_M[%s][%s]"), WINDOW_CLASS_GUID, GlobalSettings.szAppName, GlobalSettings.szCompanyName);

		// check for previous instance and attempt to see if this application is running already
		if(GlobalSettings.bSingleInstance)
		{
			m_hMainWndMutex = CreateMutex(NULL, true, m_szMainClassName);

			// limit this app to a single instance only (make sure classname is unique)
			// NOTE: using a mutex is MUCH safer than using FindWindow() alone, if the
			// exe gets executed back-to-back - as in a script - mutexes still work
			if(GetLastError() == ERROR_ALREADY_EXISTS)
			{
				HWND hWndPrevious = NULL;

				// set focus to the other main window (if possible, may not be created yet) then exit
				// if the class name is unique we don't worry about searching against the title
				if((hWndPrevious = FindWindow(m_szMainClassName, NULL)) != NULL)
					ShowWindow(hWndPrevious, SW_SHOW);
			}
			else
				bReturn = true;	// let the rest of the procedure know if it's ok to run or not
		}
		else
			bReturn = true;		// let the rest of the procedure know if it's ok to run or not

		// note: we only allow one main window per application to be created!
		if((GlobalSettings.hMainWindow == NULL) && bReturn)
		{
			WNDCLASS wc = {0};							// window's class struct
			DWORD dwWindowStyle = 0;					// style bits to use when creating the main app window
			HBRUSH hBrush = NULL;						// will contain the background color of the main window

			// set the background color to black (ignore alpha, this may be changed to whatever is needed)
			if(pBackground != NULL)
			{
				hBrush = CreateSolidBrush(RGB((byte)(pBackground->fRed * 255.0f),
					(byte)(pBackground->fGreen * 255.0f), (byte)(pBackground->fBlue * 255.0f)));
			}
			else
				// default to black
				hBrush = CreateSolidBrush(RGB(0, 0, 0));

			// we must specify the attribs for the window's class
			wc.style			= CS_HREDRAW|CS_VREDRAW|CS_OWNDC;	// style bits (CS_OWNDC very important for OGL)
			wc.lpfnWndProc		= (WNDPROC)_ProcMainWnd;			// window's message procedure to use
			wc.cbClsExtra		= 0;								// no extra data
			wc.cbWndExtra		= 0;								// no extra data
			wc.hInstance		= GlobalSettings.hEngineInstance;	// associated instance
			wc.hIcon			= NULL;								// use default icon (can be changed)
			wc.hCursor			= LoadCursor(NULL, IDC_ARROW);		// use default cursor (Windows owns it, so don't unload)
			wc.hbrBackground	= hBrush;							// background brush (don't destroy, let windows handle it)
			wc.lpszClassName	= m_szMainClassName;				// class name for main window
			wc.lpszMenuName		= NULL;								// no default menu

			// now, we can register this class
			if(RegisterClass(&wc) != 0)
			{
				rect rcWindow = {0};	// Oatmeal data type for final placement
				RECT rcStored = {0};	// Windows data type to pull from storage

				// here we determine the correct style bits to use for the man window (depends on settings), take note that
				// WS_CLIPCHILDREN and WS_CLIPSIBLINGS are important when choosing a pixel format for MS's version of OGL
				if(GlobalSettings.bFullscreen)
					dwWindowStyle = WS_CLIPSIBLINGS|WS_CLIPCHILDREN|WS_POPUP;
				else
				{
					dwWindowStyle = (bResizable) ? WS_OVERLAPPEDWINDOW|WS_CLIPCHILDREN|WS_CLIPSIBLINGS :
						WS_OVERLAPPED|WS_CAPTION|WS_SYSMENU|WS_MINIMIZEBOX|WS_CLIPCHILDREN|WS_CLIPSIBLINGS;
				}

				// figure out the bounding rectangle to use for the window
				if(pDefaultSize == NULL)
				{
					// if we get to this the caller just doesn't care where the window
					// goes (bForceSize is ignored if no size values are passed)
					if(GlobalSettings.bFullscreen)
					{
						// CW_USEDEFAULT only works for overlapped windows, so for fullscreen
						// use the resolution of the primary display as the bounding rectangle

						// note: don't use SM_CXFULLSCREEN, etc. because it won't account for the taskbar
						rcWindow.nRight = GetSystemMetrics(SM_CXSCREEN);
						rcWindow.nBottom = GetSystemMetrics(SM_CYSCREEN);
					}
					else
					{
						// get the old placement for the main window the last time this app was closed
						if(GetUserValue(NULL, WINDOW_POS_SIZE_KEY, OAT_SERIAL_BIN, &rcStored, sizeof(rcStored)))
						{
							// note: we can't use SetRect() safely b/c of the different types used
							rcWindow.nLeft = rcStored.left;
							rcWindow.nTop = rcStored.top;
							rcWindow.nRight = ((rcStored.right - rcStored.left) > 0) ? (rcStored.right - rcStored.left) : 100;
							rcWindow.nBottom = ((rcStored.bottom - rcStored.top) > 0) ? (rcStored.bottom - rcStored.top) : 100;
						}
						else
						{
							// in the instance where the user doesn't care, just let Windows
							// default to whatever it just happens to feel like currently
							rcWindow.nLeft = CW_USEDEFAULT;
							rcWindow.nRight = CW_USEDEFAULT;
						}
					}
				}
				else
				{
					// do we treat this as new data (overriding will do the same)
					if(bForceSize || !GetUserValue(NULL, WINDOW_POS_SIZE_KEY, OAT_SERIAL_BIN,
						&rcStored, sizeof(rcStored)))
					{
						// whether there is no saved data or we wish to override, treat the coords as new
						// for windowed mode, that means we'll center the window on the primary display
						rcWindow.nRight = (pDefaultSize->nWidth > 0) ? pDefaultSize->nWidth : 100;
						rcWindow.nBottom = (pDefaultSize->nHeight > 0) ? pDefaultSize->nHeight : 100;
						rcWindow.nLeft = (GlobalSettings.bFullscreen) ? 0 : (GetSystemMetrics(SM_CXSCREEN) - rcWindow.nRight) / 2;
						rcWindow.nTop = (GlobalSettings.bFullscreen) ? 0 : (GetSystemMetrics(SM_CYSCREEN) - rcWindow.nBottom) / 2;
					}
					else
					{
						// use the settings for the registry (for fullscreen top and left are always zero, no matter what)
						rcWindow.nLeft = (GlobalSettings.bFullscreen) ? 0 : rcStored.left;
						rcWindow.nTop = (GlobalSettings.bFullscreen) ? 0 : rcStored.top;
						rcWindow.nRight = ((rcStored.right - rcStored.left) > 0) ? (rcStored.right - rcStored.left) : 100;
						rcWindow.nBottom = ((rcStored.bottom - rcStored.top) > 0) ? (rcStored.bottom - rcStored.top) : 100;
					}
				}

				// use our class to try and create a main window
				GlobalSettings.hMainWindow = CreateWindow(m_szMainClassName,	// class to use
									GlobalSettings.szAppName,					// default to the application's name
									dwWindowStyle,								// style bits
									rcWindow.nLeft,								// x coord/poisition of the window
									rcWindow.nTop,								// y coord will be ignored if x coord is set to CW_USEDEFAULT
									rcWindow.nRight,							// width of the window
									rcWindow.nBottom,							// height will be ignored if width is set to CW_USEDEFAULT
									NULL,										// no parent
									NULL,										// no menu
									GlobalSettings.hEngineInstance,				// associated instance
									NULL);										// no extra data
			}

			if(GlobalSettings.hMainWindow == NULL)
			{
				// the window wasn't created, let the user know and leave
				ShowErrorMessage(IDS_ERR_CREATE_MAINWND);
				bReturn = false;
			}
			else
			{
				PIXELFORMATDESCRIPTOR pfd = {0};				// pixel descriptor struct
				bool bMaxed = false;							// flag used to pull the value of last maximized state from storage
				int32 nFormat = 0;								// index of the closest matching pixel format provided by the system
				HDC hDC = GetDC(GlobalSettings.hMainWindow);	// handle to the client DC for our window

				// get the flag to indicate if this window was maximized during the last run
				// if so, and not fullscreen then go ahead and maximize it now
				GetUserValue(NULL, WINDOW_MAX_KEY, OAT_SERIAL_INT32, &bMaxed, sizeof(bMaxed));

				// set the flag to restore the window as maximized if that's the way
				// the user left it, don't do this for fullscreen mode however
				if(!GlobalSettings.bFullscreen) m_bShowMaximized = bMaxed;

				// for those that don't know, OGL's RC on Windows really like to use the DC underneath the hood
				// this is all fine and dandy, except it means we need to make sure Windows knows about things
				// like what we intend to do with the DC so we can make sure everyone gets along with the RC

				// take note that these aren't guaranteed, just merely suggestions to Windows
				pfd.nSize = sizeof(pfd);
				pfd.nVersion = 1;
				pfd.dwFlags = PFD_DRAW_TO_WINDOW|PFD_SUPPORT_OPENGL|PFD_DOUBLEBUFFER;
				pfd.iPixelType = PFD_TYPE_RGBA;
				pfd.cDepthBits = 16;				// 16-bit depth buffer, opt for speed over precision
				pfd.iLayerType = PFD_MAIN_PLANE;

				// if we go fullscreen, use the supplied BPP, if windowed just pull the current BPP of the primary display
				if(GlobalSettings.bFullscreen)
					pfd.cColorBits = GlobalSettings.nBPP;
				else
				{
					HWND hWndDesktop = GetDesktopWindow();		// using NULL for GetWindowDC() will avoid this, but let's play safe
					HDC hDCDesktop = GetWindowDC(hWndDesktop);	// dc for the entire window on the primary display

					// set it and clean up
					pfd.cColorBits = (BYTE)GetDeviceCaps(hDCDesktop, BITSPIXEL);
					ReleaseDC(hWndDesktop, hDCDesktop);
				}

				// send Windows our suggestion and see what it let's us get away with
				nFormat = ChoosePixelFormat(hDC, &pfd);
				if(nFormat == 0)
				{
					// if we can't find a format to use, let the app run but tell the user
					ShowErrorMessage(IDS_ERR_PIXFORMAT);
				}
				else
				{
					// set the dc to the format Windows gave us to use
					SetPixelFormat(hDC, nFormat, &pfd);
					bReturn = true;
				}

				// clean up
				ReleaseDC(GlobalSettings.hMainWindow, hDC);
			}
		}
	}
	else
	{
		// let the caller know the init routine wasn't called
		ShowErrorMessage(IDS_ERR_NO_INIT);
	}

	// let the caller know the status
	return bReturn;
}

///////////////////////////////////////////////////////////////////////////////////////////////////

/*/
/ / void
/ /		DoWindowingCleanUp (void)
/ /
/ / PURPOSE:
/ /		This procedure should handle any clean ups neccessary for the
/ /		windowing routines. Its scope should not extend beyound that.
/*/

OATINLINE(void)
apiSetEventOnClose (const OATONCLOSEPROC pCallback)
{
	// null is valid in case the caller wishes to unset the callback
	m_pfOnClose = pCallback;
}

///////////////////////////////////////////////////////////////////////////////////////////////////

/*/
/ / void
/ /		DoWindowingCleanUp (void)
/ /
/ / PURPOSE:
/ /		This procedure should handle any clean ups neccessary for the
/ /		windowing routines. Its scope should not extend beyound that.
/*/

OATINLINE(bool)
apiSetMainWindowAspectRatio (const bool bAspectRatio)
{
	bool bReturn = false;

	// we need a window and don't even bother with this if we're fullscreen
	if((GlobalSettings.hMainWindow != NULL) && !GlobalSettings.bFullscreen)
	{
		LONG lStyles = GetWindowLong(GlobalSettings.hMainWindow, GWL_STYLE);

		// first and foremost, make sure the user is even allowed to resize
		// the main window, if not then it's pointless to continue with this
		if(((lStyles & WS_MAXIMIZEBOX) == WS_MAXIMIZEBOX) || (m_fAspectRatio > 0.0f))
		{
			// set the toggle on or off as to wether or not we maintain
			// a consistent aspect ratio, 0.0 means don't keep track
			// other we go ahead and calc now for later use when sizing
			if(bAspectRatio)
			{
				RECT rcAR = {0};

				// use the current dimensions to get the ratio (don't divide by zero)
				GetWindowRect(GlobalSettings.hMainWindow, &rcAR);

				// get the ratio of width vs. height
				m_fAspectRatio = ((rcAR.bottom - rcAR.top) > 0) ?
					(float32)(rcAR.right - rcAR.left) / (float32)(rcAR.bottom - rcAR.top) :
					(float32)(rcAR.right - rcAR.left) / 1.0f;

				// we have two options at this point when it comes to maximizing the window
				// and wishing to maintain the aspect ratio, either have a maximized window
				// not cover all the screen (looks tacky IMO) or just disallow maximizing
				lStyles &= ~WS_MAXIMIZEBOX;
				SetWindowLong(GlobalSettings.hMainWindow, GWL_STYLE, lStyles);
			}
			else
			{
				// reset the container and allow the main window to be resized again
				m_fAspectRatio = 0.0f;
				lStyles |= WS_MAXIMIZEBOX;
				SetWindowLong(GlobalSettings.hMainWindow, GWL_STYLE, lStyles);
			}

			bReturn = true;
		}
	}

	// show an error message to the caller if something went wrong
	if(!bReturn) ShowErrorMessage(IDS_ERR_ASPECT_MAINWND);

	return bReturn;
}

///////////////////////////////////////////////////////////////////////////////////////////////////

/*/
/ / LRESULT CALLBACK
/ /		_MainWndProc (HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
/ /
/ /		hWnd = handle to the window the message belongs to
/ /		uMsg = message to process
/ /		wParam = word sized param who's value depends on the message
/ /		lParam = long sized param who's value depends on the message
/ /
/ / PURPOSE:
/ /		Window procedure to handle messages for the main window.
/*/

OATINLINE(bool)
apiSetMainWindowIcon (const uint32 nResourceID)
{
	bool bReturn = false;

	// only attempt if the caller has already created a main window
	if((GlobalSettings.hMainWindow != NULL) && ((nResourceID > 0) && (nResourceID <= 0xFFFF)))
	{
		// load an instance handle for the executable that loaded this dll
		HINSTANCE hInst = (HINSTANCE)GetModuleHandle(NULL);

		// if we have previously loaded icons, unload the old ones them first
		if(m_hMainIcon != NULL)
		{
			SendMessage(GlobalSettings.hMainWindow, WM_SETICON, (WPARAM)ICON_SMALL, (LPARAM)NULL);
			DestroyIcon(m_hMainIcon);
		}

		if(m_hMainIconSmall != NULL)
		{
			SendMessage(GlobalSettings.hMainWindow, WM_SETICON, (WPARAM)ICON_BIG, (LPARAM)NULL);
			DestroyIcon(m_hMainIcon);
		}

		// load the (new) icons we wish to use for the main window
		m_hMainIcon = (HICON)LoadImage(hInst, MAKEINTRESOURCE(nResourceID),
			IMAGE_ICON, GetSystemMetrics(SM_CXICON), GetSystemMetrics(SM_CYICON), LR_DEFAULTCOLOR);

		m_hMainIconSmall = (HICON)LoadImage(hInst, MAKEINTRESOURCE(nResourceID),
			IMAGE_ICON, GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON), LR_DEFAULTCOLOR);

		// use the custom icons for the titlebar and when using Alt+Tab key
		SendMessage(GlobalSettings.hMainWindow, WM_SETICON, (WPARAM)ICON_BIG, (LPARAM)m_hMainIcon);
		SendMessage(GlobalSettings.hMainWindow, WM_SETICON, (WPARAM)ICON_SMALL, (LPARAM)m_hMainIconSmall);

		bReturn = true;
	}
	else
		// let the caller know something is wrong
		ShowErrorMessage(IDS_ERR_NO_MAINICON);

	return bReturn;
}

///////////////////////////////////////////////////////////////////////////////////////////////////

/*/
/ / LRESULT CALLBACK
/ /		_MainWndProc (HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
/ /
/ /		hWnd = handle to the window the message belongs to
/ /		uMsg = message to process
/ /		wParam = word sized param who's value depends on the message
/ /		lParam = long sized param who's value depends on the message
/ /
/ / PURPOSE:
/ /		Window procedure to handle messages for the main window.
/*/

OATINLINE(void)
apiSetMainWindowTitle (const tchar *szTitle)
{
	// only attempt if the caller has already created a main window, null is
	// acceptable for the title in case the caller decides to clear it
	if(GlobalSettings.hMainWindow != NULL) SetWindowText(GlobalSettings.hMainWindow, szTitle);
}

///////////////////////////////////////////////////////////////////////////////////////////////////

/*/
/ / LRESULT CALLBACK
/ /		_MainWndProc (HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
/ /
/ /		hWnd = handle to the window the message belongs to
/ /		uMsg = message to process
/ /		wParam = word sized param who's value depends on the message
/ /		lParam = long sized param who's value depends on the message
/ /
/ / PURPOSE:
/ /		Window procedure to handle messages for the main window.
/*/

OATINLINE(void)
apiShowMainWindow (const bool bVisible)
{
	// only attempt if the caller has already created a main window
	if(GlobalSettings.hMainWindow != NULL)
	{
		// "geniuses" at MS disallow setting the max state of a window that's invisible
		// so when we restore the old window placement, rather than do the proper thing
		// and set the state when the window is created (init as invisble to allow for
		// loading of the app), no we get to check here with every call to this routine
		if(m_bShowMaximized && bVisible)
		{
			ShowWindow(GlobalSettings.hMainWindow, SW_SHOWMAXIMIZED);
			m_bShowMaximized = false;
		}
		else
			ShowWindow(GlobalSettings.hMainWindow, (bVisible) ? SW_SHOW : SW_HIDE);
	}
}

///////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////// PUBLIC ROUTINES /////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////

/*/
/ / void
/ /		DoPostWindowingCleanUp (void)
/ /
/ / PURPOSE:
/ /		This procedure should handle any clean ups neccessary for the
/ /		windowing routines. Its scope should not extend beyound that.
/*/

void
DoPostWindowingCleanUp (void)
{
	// note: for a DLL only and on NT-based versions of Windows a window's
	// class does not get automatically unregistered, so do this manually
	if(GlobalSettings.hMainWindow != NULL) UnregisterClass(m_szMainClassName, GlobalSettings.hEngineInstance);

	// unload any previously loaded icons for the main window
	if(m_hMainIcon != NULL) DestroyIcon(m_hMainIcon);
	if(m_hMainIconSmall != NULL) DestroyIcon(m_hMainIcon);

	// if we created a mutex to only allow a single instance, remove it now
	if(m_hMainWndMutex != NULL) CloseHandle(m_hMainWndMutex);
}

///////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////// PRIVATE ROUTINES /////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////

/*/
/ / bool
/ /		_GoFullscreen (uint32 nWidth, uint32 nHeight, uint8 nBPP, uint8 nVertRefresh)
/ /
/ /		nWidth = width to set the display's resolution to
/ /		nHeight = height to set the display's resolution to
/ /		nBits = bits per pixel to use
/ /		nVertRefresh = vertical refresh rate to use
/ /
/ / PURPOSE:
/ /		Sets the display to fullscreen mode.
/*/

static bool
_GoFullscreen (uint32 nWidth, uint32 nHeight, uint8 nBPP, uint8 nVertRefresh)
{
	bool bReturn = false;

	// as a safey precaution, make sure (again) the rate is in a normal range
	if((nVertRefresh >= 60) && (nVertRefresh <= 200))
	{
		DEVMODE dm = {0};

		dm.dmSize				= sizeof(DEVMODE);
		dm.dmPelsWidth			= nWidth;									// screen width
		dm.dmPelsHeight			= nHeight;									// screen height
		dm.dmBitsPerPel			= nBPP;										// display bits per pixel (BPP)
		dm.dmFields				= DM_BITSPERPEL|DM_PELSWIDTH|DM_PELSHEIGHT;	// flags
		dm.dmDisplayFrequency	= nVertRefresh;

		// attempt to move to fullscreen
		if(ChangeDisplaySettings(&dm, CDS_FULLSCREEN) == DISP_CHANGE_SUCCESSFUL) bReturn = true;
	}

	return bReturn;
}

///////////////////////////////////////////////////////////////////////////////////////////////////

/*/
/ / void
/ /		_GoWindowed (void)
/ /
/ / PURPOSE:
/ /		Sets the display to windowed mode.
/*/

static void
_GoWindowed (void)
{
	ChangeDisplaySettings(NULL, 0);	// switch back to the desktop
}

///////////////////////////////////////////////////////////////////////////////////////////////////

/*/
/ / LRESULT CALLBACK
/ /		_ProcMainWnd (HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
/ /
/ /		hWnd = handle to the window the message belongs to
/ /		uMsg = message to process
/ /		wParam = word sized param who's value depends on the message
/ /		lParam = long sized param who's value depends on the message
/ /
/ / PURPOSE:
/ /		Window procedure to handle messages for the main window.
/*/

static LRESULT CALLBACK
_ProcMainWnd (HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
	LRESULT lReturn = 0;

	switch(uMsg)
	{
		case WM_CLOSE:
			{
				// if an event was specified, allow the caller to add processing
				bool bProcess = true;
				if(m_pfOnClose != NULL) bProcess = m_pfOnClose();

				if(bProcess)
				{
					// before the main window is destroyed, we save it's position and size to user settings
					// note: do not perform this operation if the main window is closed while minimized
					if(!IsIconic(hWnd))
					{
						bool bMaxed = IsZoomed(hWnd);
						LONG lStyles = GetWindowLong(GlobalSettings.hMainWindow, GWL_STYLE);

						// make sure the user is even allowed to maximize the main window and
						// that we aren't fullscreen, if not then it's pointless to save this
						if(((lStyles & WS_MAXIMIZEBOX) == WS_MAXIMIZEBOX) && !GlobalSettings.bFullscreen)
						{
							// save a flag to indicate if this window was maximized or not
							SetUserValue(NULL, WINDOW_MAX_KEY, OAT_SERIAL_INT32, &bMaxed, sizeof(bMaxed));
						}

						// this check is more simple, just make sure the window isn't maximized before saving
						if(!bMaxed)
						{
							RECT rcWndPos;		// use the Windows data type so the info is pulled correctly

							// get the information and serialize it
							GetWindowRect(hWnd, &rcWndPos);
							SetUserValue(NULL, WINDOW_POS_SIZE_KEY, OAT_SERIAL_BIN, &rcWndPos, sizeof(rcWndPos));
						}
					}

					// destroy this window and thus the app as requested
					DestroyWindow(hWnd);
				}
			}
			break;

		case WM_CREATE:

			{
				LPCREATESTRUCT pCreate = (LPCREATESTRUCT)lParam;

				// enter fullscreen mode (if chosen) using the size of the window as the resolution
				if(GlobalSettings.bFullscreen)
					_GoFullscreen(pCreate->cx, pCreate->cy, GlobalSettings.nBPP, GlobalSettings.nVertRefresh);
			}
			break;

		case WM_DESTROY:

			// return back to windowed mode if we went to fullscreen
			if(GlobalSettings.bFullscreen) _GoWindowed();

			// send WM_QUIT and close main running thread
			PostQuitMessage(true);
			break;

		case WM_ENTERMENULOOP:

			// NOTE: always leave this whether or not the app has a menu! (remember the sys menu)
			// suspend the render thread if the user is navigating menus (if not minimized)
			//if(!IsIconic(hWnd)) PostThreadMessage(nRenderThreadID, UWM_PAUSE, true, 0);
			break;

		case WM_EXITMENULOOP:

			// NOTE: always leave this whether or not the app has a menu! (remember the sys menu)
			// start the render thread back up if the user leaves the menu (if not minimized)
			//if(!IsIconic(hWnd)) PostThreadMessage(nRenderThreadID, UWM_PAUSE, false, 0);
			break;

		case WM_GETMINMAXINFO:

			// don't allow a window to go beneath 100x75 in size, this ain't the program manager
			// in Windows 3.1, if you wan't want to see it then make it invisible
			{
				MINMAXINFO *pInfo = (MINMAXINFO *)lParam;

				// we use the message to enforce a minimum width and height of the window
				pInfo->ptMinTrackSize.x = (LONG)(100 * ((m_fAspectRatio > 0.0f) ? m_fAspectRatio : 1));
				pInfo->ptMinTrackSize.y = (LONG)100;
			}
			break;

		case WM_KEYDOWN:

/*			switch(wParam)
			{
				case VK_ESCAPE:

					// if we are in fullscreen mode only
					if(bGoFullscreen)
					{
						HMENU hMenu = NULL;
						bool bHidden = false;

						// get the handle of the menubar (if present)
						hMenu = GetMenu(hWnd);

						// to determine what gets done, we use a boolean
						// stored in the window's userdata buffer
						bHidden = (bool)GetWindowLongPtr(hWnd, GWL_USERDATA);

						// the ESC key can do one of two things in fullscreen mode, if there
						// is a menu, it will show and hide it otherwise it will exit the app
						// if the menu is hidden then we do not process this (we toggle it)
						if((hMenu == NULL) && !bHidden)
						{
							// no menu so close the application down
							SendMessage(hWnd, WM_CLOSE, 0, 0);
						}
						else
						{
							if(bHidden)
							{
								// recreate the menubar to toggle it on and show cursor
								SetMenu(hWnd, LoadMenu(GetWindowInstance(hWnd), MAKEINTRESOURCE(IDR_MAINFRAME)));
								ShowCursor(true);

								// set the userdata to false to indicate we toggled the menu
								SetWindowLongPtr(hWnd, GWL_USERDATA, false);
							}
							else
							{
								// we have a menubar that's shown, so toggle it off and hide cursor
								SetMenu(hWnd, NULL);
								ShowCursor(false);

								// set the userdata to true to indicate we toggled the menu
								SetWindowLongPtr(hWnd, GWL_USERDATA, true);
							}
						}
					}
					break;
			}*/
			break;

		case WM_SIZE:

			switch(wParam)
			{
				case SIZE_MINIMIZED:

					// play nice and don't hog the computer if minimized
//					PostThreadMessage(nRenderThreadID, UWM_PAUSE, true, 0);
					break;

				case SIZE_RESTORED:

					// we need to resize the viewport for OGL, but it must be done in the
					// context of the render thread, so set the signal to be picked up
//					PostThreadMessage(nRenderThreadID, UWM_RESIZE, 0, 0);

					// back in action, so let the threads continue
//					PostThreadMessage(nRenderThreadID, UWM_PAUSE, false, 0);
					break;
			}
			break;

		case WM_SIZING:

			// only maintain the aspect ratio if it has been set
			if(m_fAspectRatio > 0.0f)
			{
				LPRECT pSize = (LPRECT)lParam;

				switch(wParam)
				{
					case WMSZ_LEFT:
					case WMSZ_RIGHT:

						// use the width and aspect ratio to calc a new height
						pSize->bottom = pSize->top +
							(int32)((float32)(pSize->right - pSize->left) / m_fAspectRatio);
						break;

					case WMSZ_TOP:
					case WMSZ_BOTTOM:

						// use the height and aspect ratio to calc a new width
						pSize->right  = pSize->left +
							(int32)((float32)(pSize->bottom - pSize->top) * m_fAspectRatio);
						break;

					case WMSZ_TOPLEFT:
					case WMSZ_BOTTOMLEFT:

						// use the height and aspect ratio to calc a new width
						pSize->left = pSize->right -
							(int32)((float32)(pSize->bottom - pSize->top) * m_fAspectRatio);
						break;

					case WMSZ_TOPRIGHT:
					case WMSZ_BOTTOMRIGHT:

						// use the height and aspect ratio to calc a new width
						pSize->right  = pSize->left +
							(int32)((float32)(pSize->bottom - pSize->top) * m_fAspectRatio);
						break;
				}
			}
			break;

		default:
			lReturn = DefWindowProc(hWnd, uMsg, wParam, lParam);
	}

	return lReturn;
}

////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////

#endif // _WIN32